module Katello
  class Pool < Katello::Model
    include Katello::Authorization::Pool
    belongs_to :subscription, :inverse_of => :pools, :class_name => "Katello::Subscription"

    has_many :pool_products, :class_name => "Katello::PoolProduct", :dependent => :destroy, :inverse_of => :pool
    has_many :products, :through => :pool_products, :class_name => "Katello::Product"

    has_many :pool_activation_keys, :class_name => "Katello::PoolActivationKey", :dependent => :destroy, :inverse_of => :pool
    has_many :activation_keys, :through => :pool_activation_keys, :class_name => "Katello::ActivationKey"

    has_many :subscription_facet_pools, :class_name => "Katello::SubscriptionFacetPool", :dependent => :delete_all
    has_many :subscription_facets, :through => :subscription_facet_pools

    belongs_to :organization, :class_name => 'Organization', :inverse_of => :pools
    belongs_to :hypervisor, :class_name => 'Host::Managed', :inverse_of => :hypervisor_pools

    scope :in_organization, ->(org_id) { where(:organization_id => org_id) }
    scope :for_activation_key, ->(ak) { joins(:activation_keys).where("#{Katello::ActivationKey.table_name}.id" => ak.id) }
    scope :expiring_in_days, ->(days) do
      return self if days.blank?
      where(["end_date < ?", days.to_i.days.from_now.end_of_day])
    end
    scope :upstream, -> { where.not(upstream_pool_id: nil) }
    scope :redhat, -> { joins(:products).merge(Katello::Product.redhat).distinct }

    include Glue::Candlepin::CandlepinObject
    include Glue::Candlepin::Pool

    scoped_search :on => :cp_id, :complete_value => true, :rename => :id, :only_explicit => true
    scoped_search :on => :upstream_pool_id, :complete_value => true, :only_explicit => true
    scoped_search :on => :quantity, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :start_date, :complete_value => true, :rename => :starts, :only_explicit => true
    scoped_search :on => :end_date, :complete_value => true, :rename => :expires, :only_explicit => true
    scoped_search :on => :ram, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :multi_entitlement, :complete_value => true
    scoped_search :on => :consumed, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :account_number, :complete_value => true, :rename => :account, :validator => ScopedSearch::Validators::INTEGER
    scoped_search :on => :contract_number, :complete_value => true, :rename => :contract, :validator => ScopedSearch::Validators::INTEGER
    scoped_search :on => :virt_who, :complete_value => true, :only_explicit => true
    scoped_search :on => :name, :relation => :subscription, :complete_value => true, :rename => :name
    scoped_search :on => :support_level, :relation => :subscription, :complete_value => true
    scoped_search :on => :sockets, :relation => :subscription, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :cores, :relation => :subscription, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :cp_id, :relation => :subscription, :complete_value => true, :only_explicit => true, :rename => :product_id

    scoped_search :on => :stacking_id, :complete_value => true, :only_explicit => true
    scoped_search :on => :instance_multiplier, :relation => :subscription, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
    scoped_search :on => :pool_type, :complete_value => true, :rename => :type

    validates_lengths_from_database

    validates :subscription, :presence => true
    validates :organization, :presence => true

    validate :subscription_matches_organization

    def subscription_matches_organization
      errors.add(:base, _("A Pool and its Subscription cannot belong to different organizations")) unless subscription&.organization_id == self.organization_id
    end

    DAYS_RECENTLY_EXPIRED = 30

    def redhat?
      self.class.redhat.where(:id => self.id).exists?
    end

    def active?
      active
    end

    # used for notification bell
    def expiring_soon?
      days_until_expiration >= 0 &&
        days_until_expiration <= Setting[:expire_soon_days].to_i
    end

    # used for entitlements report template
    def days_until_expiration
      (end_date.to_date - Date.today).to_i
    end

    def recently_expired?
      Date.today >= end_date.to_date && (Date.today - end_date.to_date) <= DAYS_RECENTLY_EXPIRED
    end

    def quantity_available
      return -1 if self.quantity == -1
      return 0 unless self.quantity && self.consumed
      self.quantity - self.consumed
    end

    def type
      self.pool_type
    end

    def upstream?
      upstream_pool_id.present?
    end

    def import_audit_record(old_host_ids, new_host_ids = subscription_facets.pluck(:host_id))
      return if old_host_ids.empty? && new_host_ids.empty?
      pool_id = self.id
      Audited::Audit.new(
        :auditable_id => pool_id,
        :auditable_type => 'Katello::Pool',
        :action => 'update',
        :auditable_name => self.name,
        :audited_changes => {'host_ids' => [old_host_ids, new_host_ids]}
      ).save!
    end

    # Note - Audit hook added to find records based on column except associations to display audit information
    def self.audit_hook_to_find_records(keyname, change, _audit)
      if keyname =~ /_ids$/
        case keyname
        when 'host_ids'
          ::Host.where(:id => change)&.index_by(&:id)
        end
      end
    end

    private

    def default_sort
      Pool.joins(:subscription).order("subscription.name")
    end

    apipie :class, desc: "A class representing #{model_name.human} object" do
      name 'Pool'
      refs 'Pool'
      sections only: %w[all additional]
      prop_group :katello_idname_props, Katello::Model, meta: { friendly_name: 'pool' }
      property :available, Integer, desc: 'Returns number of available subscriptions'
      property :quantity, Integer, desc: 'Returns number of all subscriptions'
      property :consumed, Integer, desc: 'Returns number of consumed subscriptions'
      property :product_id, String, desc: 'Returns ID of the product'
      property :account_number, Integer, desc: 'Returns subscription account number'
      property :contract_number, Integer, desc: 'Returns subscription contract number'
      property :type, String, desc: 'Returns type of the pool, e.g. "NORMAL"'
      property :organization, 'Organization', desc: 'Returns organization to which the pool belongs'
      property :start_date, ActiveSupport::TimeWithZone, desc: 'Returns subscription start date'
      property :end_date, ActiveSupport::TimeWithZone, desc: 'Returns subscription end date'
      property :days_until_expiration, Integer, desc: 'Returns number of days until expiration'
    end
    class Jail < ::Safemode::Jail
      allow :id, :name, :available, :quantity, :product_id, :contract_number, :type, :account_number, :start_date, :end_date, :organization, :consumed, :days_until_expiration
    end
  end
end

class ActiveRecord::AssociationRelation::Jail < Safemode::Jail
  allow :sort_by
end
